AuntieDialog

Pete Gontier
Apple Carbon High Level Toolbox Engineering
© 1999-2000 Apple Computer, Inc.



Introduction

AuntieDialog is a module, provided in source code form, which replaces some key Dialog Manager calls with calls to Control Manager and Window Manager, thus freeing you from struggling with the Dialog Manager API, yet allowing you to use the familiar tools you've always used to build dialogs.

But Why?

For several squillion years, developers have chafed at the limitations of the Dialog Manager. In fact, there's an entire Technote about how badly the Dialog Manager sucks. The Technote suggests that rather than attempt to bludgeon the Dialog Manager into working the way you need it to, you should use Window Manager and Control Manager. That's a fine suggestion, except that popular dialog creation tools assume you'll be using the Dialog Manager to manage your dialogs, and there are no Human Interface Toolbox (HIT) calls outside of Dialog Manager which read dialog resources.

Recommended Uses

AuntieDialog is best suited for use by new code, although in the long run you might be better off investing the time in getting rid of your existing Dialog Manager code as well. (For you conspiracy theorists out there, this does not mean Apple is considering abolishing the Dialog Manager, as much as we might like to.) Another thing AuntieDialog does not imply is that you should abandon the use of such high-level calls as StandardAlert. Dialog Manager is perfect for use by such high-level calls, which present a simple user interface. Dialog Manager's limitations really begin to show when you try to build a more complicated user interface on top of it.

Limitations

AuntieDialog is not a Dialog Manager replacement. Remember, Dialog Manager sucks. There'd be no point in slavishly emulating it. Instead, AuntieDialog offers more flexibility and fewer limitations, but you're not going to be able to drop this code into your project and blow away all instances of #include <Dialogs.h> without doing some work.

For example, AuntieDialog provides no way to designate a window as system-modal. (In fact, there's never been a way to make an arbitrary window system-modal; the fact that AuntieDialog cannot be used with ModalDialog just makes the problem more evident.) In other words, you can't post a window which prevents the user from switching to another application. You can look at this as a limitation of AuntieDialog or you can see it as an opportunity to provide a better user experience for your users. We think system-modal windows are almost never in the user's best interest. And if you do find an exceptional case worthy of a system-modal window, it's likely to be very simple, so AuntieDialog is probably not called for anyway &emdash; just use Dialog Manager for such dialogs.

Compatibility

You need to query Gestalt to make sure Appearance is present before calling AuntieDialog. Consult the DTS Q&A "Appearance Versions" for more info, and of course do your own testing of any program which incorporates this code. At present, AuntieDialog requires CarbonLib, which implies Appearance, but AuntieDialog may some day support InterfaceLib apps.

Data Types

There are no new data types in AuntieDialog. Isn't that a relief? I've designed this thing to stay out of your way and let you use the Window Manager and Control Manager to the fullest extent allowed by law. There are some function pointer types, but I'm feeling like a lawyer this evening, so I'm going to claim these are not data types.

Resources

I think you'll see a pattern developing here: there are no new resource formats in AuntieDialog. Here's how AuntieDialog treats the Dialog Manager resources:

resource type

what is it?

AuntieDialog behavior


DLOG

dialog template

fully supported in nearly the same way as Dialog Manager

DITL

dialog item list

fully supported in nearly the same way as Dialog Manager

CNTL

control template

fully supported in nearly the same way as Dialog Manager

dftb

new-style
dialog item color and text style table

fully supported in nearly the same way as Dialog Manager


ictb

old-style
dialog item color and text style table

ignored in favor of dftb to maximize Appearnce-savviness and minimize implementation complexity

dctb

dialog color table

ignored in favor of maximizing Appearance-savviness

dlgx

dialog template extension

ignored in favor of maximizing Appearance-savviness

ALRT

alert template

not supported; AuntieDialog does not make the distinction between alerts and dialogs

alrx

alert template extension

not supported; AuntieDialog does not make the distinction between alerts and dialogs

actb

alert item color and text style table

not supported; AuntieDialog does not make the distinction between alerts and dialogs


One other thing regarding resources bears repeating and clarification: the policy of the HIT with respect to releasing resources has always been that all resources are assumed purgeable and are never released (though it's safe for an HIT client to explicitly release resources as soon as that client is certain HIT no longer needs those resources). Although we can make some pretty good guesses at the reasoning behind this policy, it's difficult to explain conclusively because there's not a lot of documentation about the smaller design decisions made during the development of the original Macintosh. In any case, AuntieDialog adopts the same policy in order to avoid creating any additional confusion during your debugging sessions.

AuntieDialog and Control IDs

Understanding how AuntieDialog uses control IDs is critical. This usage is designed to be as unobtrusive as possible while at the same time enabling you to refer to controls with index values like you would refer to dialog items. If you would like to use other values, you need to be aware of the facilities AuntieDialog provides so you can make an informed decision as to how to choose those values.

The Default Behavior and the Values it Produces

When AuntieDialog appends a list of controls to a window, it assigns each control an ID immediately after creating the control. The signature of the ID is kAuntieDialogSignature ('aunt', a value in the Apple-only range).

To choose 'id' field of the ID, AuntieDialog counts the controls which are already in the window (with some exceptions we'll get to in a moment). The first new control gets the count + 1, the second control gets the count + 2, and so on. So, when you add controls to a window which has none, the first control will have ID 1, the second will have ID 2, and so on. If you add controls to a window which already has three controls, the first control will have ID 4, the second 5, and so on.

Any control whose ID signature is not kAuntieDialogSignature does not contribute to the count of controls in a window. You might wonder where such controls would come from, since AuntieDialog always sets the ID signature of the controls it creates to kAuntieDialogSignature. First, the root control does not appear in any dialog item list, is created by AuntieDialog automagically, but is not given kAuntieDialogSignature. Second, controls which are created by other controls, for example the scroll bars created by a list box control, have no ID signature. Since for obscure reasons AuntieDialog cannot predict where in the control hierarchy these controls will appear, it makes no attempt to assign them ID signatures, which is probably what you want anyway.

Note: You can expect standard system controls created by other controls to have no ID signature, but you may encounter custom controls which have other behavior. You may need to set the ID signature of these controls to 0 yourself. If it isn't possible to change the ID signature for such a control, that control is probably not compatible with AuntieDialog.


AuntieDialog's default control ID is exactly the behavior you want if you want ID values which correspond to dialog item index values. With a window full of controls whose IDs conform to the default behavior of AuntieDialog, you can use the function GetControlByID to get a ControlRef in much the same manner as you would use GetDialogItemAsControl.

But you must accept one more restriction in order to make this work: If you plan to dispose some controls and append others &emdash; say, when the user clicks a non-current tab in a tabs control &emdash; you should only dispose controls whose IDs are at the end of the range of values for controls in a given window. This ensures the controls you later append will acquire a range of IDs which is contiguous with the controls which remain after the deletion. The Dialog Manager doesn't allow you to delete items from the middle of the list, but Control Manager gives you the freedom to dispose any control at any time, so you need to be aware of the effect it will have on AuntieDialog. Deleting controls whose ID is not at the end of the range for a given window creates the possibility of duplicate IDs, which would make using GetControlByReference somewhat less than enjoyable. You need to delete controls in much the same way as you would shorten a dialog item list with ShortenDITL.

Sidebar: There are some very credible people, one of whom owns the Control Manager as of this writing, who believe deleting and recreating controls is an inherently risky endeavor. One problem scenario is when a user clicks a non-current tab in a tabs control under adverse conditions such as low memory. If the controls for the tab which is becoming current cannot be created, the operation will fail; the obvious fall-back position is to recreate the controls for the original tab. Unfortunately, this operation may also fail. Now the fallback position is to close the window and apologize to the user. However, at this point we've involved a lot of code to handle exceptional cases, and as most experienced software engineers know, this kind of code tends to get the least testing and is most likely to blow up. The people who concern themselves with this issue think it's much better to create all the controls you'll ever need as you bring up a window and hide and show them as needed. Nothing in AuntieDialog prevents or discourages you from using this technique. And from the system's perspective, this is a non-issue; the system doesn't care why you are deleting and creating controls; the responsibility for pondering this matter falls squarely into your lap.

How to Override the Default Behavior

OK, the default behavior is all well and good, but suppose you're ready to go beyond simulated dialog item list indexes. If you want control IDs to follow some other logic (say, pointers to instances of a C++ class), you should probably specify a non-NIL control creation function:

typedef pascal
OSStatus (*ControlCreationProcPtr) (ControlRef);

You write a function of the above type, and it is called from inside AppendDialogItemsAsControls after each control is created. (The control's ID has already been assigned according to the default behavior described above.) Your function decides whether and how to change the control, including disposing it, based on the control itself and whatever other criteria you choose.

You can, of course, change the ID of any control (using SetControlID), including those created by AuntieDialog, but you will probably want to make sure the value is unique with respect to a given window if you want GetControlByID to continue working well.

Note that addresses of heap blocks (including those produced by NewPtr, NewHandle, malloc, and operator new) are unique even beyond the context of a given window, so if you want to associate the address of a heap block (say, a C++ object pointer) with a control, that address will be suitable for use as a search key for GetControlByID. Be sure, though, that all controls in a given window have IDs which are unique with respect to each other. For example, don't expect a C++ object pointer to be a useful control ID if the rest of the control IDs for the controls in the given window are still dialog item list indexes. (Not that a C++ object pointer would ever be as low as a dialog item list index, but you should still be careful.)

This callback function should not allow C++ exceptions to propagate.

Functions

The AuntieDialog package contains functions for creating a window full of controls, for appending lists of controls, for removing child controls, for counting the controls in a window, and for handling events in such a window. The rest is up to you and Window Manager and Control Manager.

CountControlsInWindow

OSStatus CountControlsInWindow (WindowRef, UInt16 *);

This function is roughly analagous to CountDITL. It counts the number of controls in a window, excluding the root control and any controls whose ID signature is not kAuntieDialogSignature. If the window has no root control, this function fails with an error. Controls with ID signatures other than kAuntieDialogSignature are skipped under the assumption that they are created by other controls, such as the scroll bar controls created by the list box control, and not by AuntieDialog. (Keep this is mind if you assign your own ID values.) You may find this function useful if you adopt the AuntieDialog convention of storing dialog item list indexes as control ID values. If you need to know the ID of the first control which would be added by calling AppendDialogItemsAsControls, call CountControlsInWindow and add one to the result.

AppendDialogItemsAsControls

OSStatus AppendDialogItemsAsControls
   (short resID, WindowRef, ControlCreationProcPtr);

This function is roughly analgous to AppendDialogItemList. The chief difference, of course, is that this function creates controls, not dialog items. Another important difference is that this function makes no attempt to place the new controls anywhere in particular within the window or resize the window to fit the new controls. The dialog item list template whose ID you specify contains the final location of the controls, and they must fit in the window if you want them to be visible to the user.

AppendDialogItemsAsControls is called by NewAuntieDialog.

AppendDialogItemsAsControls inhibits control drawing and invalidates control regions if the parent window is visible to prevent some controls from drawing before others. So, if you have a window for which you must call GetAuntieDialog and then AppendDialogItemsAsControls before allowing the user to interact with the window, you won't have to work around the bizarre non-linear drawing behavior you would have gotten from Dialog Manager.

AppendDialogItemsAsControls auto-embeds controls just as Dialog Manager does.

Unlike Dialog Manager, when AppendDialogItemsAsControls creates user pane controls based on dialog user items, the user pane controls support embedding, so if you need a user pane control which doesn't support embedding, you'll need to create an appropriate control resource.

If you pass a NIL reference provider, AppendDialogItemsAsControls will append controls whose IDs have been assigned in the default manner.

NewAuntieDialog

OSStatus NewAuntieDialog
   (short resID, WindowRef window, ControlCreationProcPtr);

This function is roughly analgous to NewDialog. NewAuntieDialog calls AppendDialogItemsAsControls and is called by GetAuntieDialog. The resource ID specifies the dialog item list resource which is to be used as a template. This function allows you to create your own window, perhaps with an API such as CreateNewWindow, and pass it to the AuntieDialog package to be populated with controls. This function will fail if the window already contains controls.

GetAuntieDialog

OSStatus GetAuntieDialog
    (short resID, WindowRef behind,
       ControlCreationProcPtr, WindowRef *result);

GetAuntieDialog is roughly analagous to GetNewDialog. resID is the resource ID of the dialog resource from which you want to build a window. behind is the window behind which you wish the new window to appear. And result is where the reference to the newly created window will be put.

GetAuntieDialog produces a WindowRef, not a DialogRef. Don't expect the window that's produced to be recognized by the Dialog Manager. It's not a dialog. That would defeat the purpose of AuntieDialog.

GetAuntieDialog always creates a color window and ignores any dialog color table resource in favor of the colors and patterns in the theme the user has chosen. GetAuntieDialog also ignores any dialog extension resource and creates the window as if such a resource with all its bits set is present, which means you always get a theme background and you always get theme controls. (The remaining bits are handled elsewhere in AuntieDialog or by your program.)

GetAuntieDialog ignores any extra bits on the end of the dialog template resource which the Dialog Manager would have used to align the window. The support for these bits has never been well-defined or bug-free, and RepositionWindow, an API introduced in Mac OS 8.5, is so vastly superior to the old alignment support that I decided it was better to leave the issue unaddressed.

GetAuntieDialog always creates the window initially invisible and only shows it (if the dialog resource specifies the window is to be shown) after creating all the controls. This minimizes flicker.

DisposeChildControls

OSStatus DisposeChildControls (ControlRef);

This function disposes the child controls of the given parent control. This will be useful mostly in conjunction with AppendDialogItemsAsControls; you may wish to delete some controls and append some others, for example when the user clicks a new tab in a tab control you're using as a parent control.

This function differs from DisposeControl in that DisposeControl would dispose the parent in addition to the children.

This function differs from ShortenDITL not only in that it operates on controls and not dialog items but also in that it removes child controls regardless of where they appeared in the dialog item list before they became controls. This may create holes in the range of control IDs. You may want to make sure that the child controls you dispose appear at the end of the dialog item list they came from. That way, when you append other controls, their IDs will be in sequence with the rest of the controls in the window.



To do:

  • Add more application-level logic to MegaDialog simulation.

  • Describe all the functions, including the callbacks.

  • Passing the root control to DeactivateControl to propagate latent grayness doesn't always work properly on older systems. Should we work around this?

  • AuntieDialogGetIdleInterval should traverse every control in every AuntieDialog window to determine which control would request the shortest idle if it could.

  • Finish the compatibility hacks in ShouldDrawControlsOffScreen.

  • Work around Radar 2459917 (not enough mouse-moved events)